package com.tsd.core.config;

import cn.hutool.core.map.MapUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.db.Db;
import cn.hutool.db.DbUtil;
import cn.hutool.db.Entity;
import cn.hutool.db.sql.SqlExecutor;
import com.alibaba.druid.pool.DruidDataSource;
import com.tsd.core.constants.Constants;
import com.tsd.core.datasource.DataSourceContextHolder;
import com.tsd.core.datasource.DataSourceInitService;
import com.tsd.core.datasource.DataSourceUtils;
import com.tsd.core.datasource.DynamicDataSource;
import com.tsd.core.utils.ExceptionUtil;
import com.tsd.core.utils.FlywayUtil;
import com.tsd.core.utils.HlpUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @ClassName: FlywayConfig
 * @Description: TODO
 * @Author: Hillpool
 * @Date: 2022/9/7 10:49
 * @Version: 1.0
 */
@Configuration
@DependsOn("springContextUtil")
@PropertySource(value = {"classpath:flyway.properties"}, ignoreResourceNotFound = true)
@Order(Ordered.HIGHEST_PRECEDENCE)
public class FlywayConfig implements CommandLineRunner {

    public static final Logger LOGGER = LoggerFactory.getLogger(FlywayConfig.class);

    @Resource
    private DruidDataSource masterDataSource;

    @Override
    public void run(String... args) {
        LOGGER.info("初始化Flyway");
        this.migrateFlyway(null);
    }

    public Map<Object, String> migrateFlyway(Map<Object, Object> dynamicDataSourceMap) {
        FlywayUtil.copyJarFlyway2Classpath();
        if (dynamicDataSourceMap == null) {
            dynamicDataSourceMap = DynamicDataSource.getTargetDataSource();
        }
        Map<Object, String> retMap = new HashMap<>(dynamicDataSourceMap.size());
        Db db = DbUtil.use(this.masterDataSource);
        if (!dynamicDataSourceMap.isEmpty()) {
            Set<Object> dataSourceKey = dynamicDataSourceMap.keySet();
            for (Object key : dataSourceKey) {
                LOGGER.info("{}：开始执行初始化", key);
                DataSource dataSource = (DataSource) dynamicDataSourceMap.get(key);
                Entity entity = DataSourceInitService.getAccountSet(key);
                if (entity == null) {
                    continue;
                }
                try {
                    FlywayUtil.clearSqlFile(entity);
                    FlywayUtil.procSqlFile2AccountSet4Mode(dataSource, entity);
                    this.doFlyway(entity, dataSource);
                    if (DataSourceContextHolder.DEFAULT_ACCOUNT_SET_SID.equals(key)) {
                        this.initMasterAccountSetData(db, entity);
                    } else {
                        this.initAccountSetAdmin(dataSource, entity);
                    }
                    LOGGER.info("{}：结束执行Flyway", key);
                    retMap.put(key, Constants.VALUE_SUCCESS);
                } catch (Exception e) {
//                    String ss = e.getClass().getName();
//                    org.flywaydb.core.internal.sqlscript.FlywaySqlScriptException;
                    String msg = e.getMessage();
                    retMap.put(key, msg);
                    LOGGER.error("Flyway执行失败：{}", key);
                    LOGGER.error(msg);
                }
            }
            // 建日志表
            for (Object key : dataSourceKey) {
                Entity entity = DataSourceInitService.getAccountSet(key);
                if (entity == null) {
                    continue;
                }
                if (entity.containsKey("log") && entity.getBool("log")) {
                    DataSource dataSource = (DataSource) dynamicDataSourceMap.get(key);
                    DataSourceUtils.createTableLogInfo4AccountSet(dataSource);
                }
            }
        }
        FlywayUtil.clearJarFlywayFromClasspath();
        return retMap;
    }

    public void doFlyway(Entity entity, DataSource dataSource) {
        try {
            String locations = String.format("classpath:db/migration/account_%s", entity.getStr("code"));
            FlywayUtil.doFlyway(dataSource, locations);
        } finally {
            FlywayUtil.clearSqlFile(entity);
        }
    }

    private void initMasterAccountSetData(Db db, Entity entity) {
        try {
            Entity newEntity = entity.clone();
            newEntity.remove("log");
            newEntity.remove("id");
            Date now = new Date();
            newEntity.setTableName(Constants.TABLE_NAME_SYS_ACCOUNT_SET);
            Entity existEntity = DataSourceUtils.getAccountSetBySid(db, entity.getStr("sid"));
            if (existEntity != null) {
                newEntity.put("update_date", now);
                Entity where = new Entity(Constants.TABLE_NAME_SYS_ACCOUNT_SET);
                where.put("id", existEntity.getLong("id"));
                db.update(newEntity, where);
            } else {
                newEntity.put("create_date", now);
                newEntity.put("create_begin", now);
                newEntity.put("create_end", now);
                db.insert(newEntity);
            }
        } catch (Exception e) {
            LOGGER.error(ExceptionUtil.getStackTraceFromException(e));
        }
    }

    private void initAccountSetAdmin(DataSource dataSource, Entity entity) {
        if (!entity.containsKey("code")) {
            return;
        }
        if (entity.containsKey("log") && entity.getBool("log")) {
            return;
        }
        try {
            String code = entity.getStr("code");
            LOGGER.info("{}：初始化管理员", code);
            String pwd = HlpUtils.doMd5(SecureUtil.md5(code));
            Db db = DbUtil.use(dataSource);
            String sql = String.format("{CALL init_admin('%s', '%s')}", code, pwd);
            SqlExecutor.call(db.getConnection(), sql);
        } catch (Exception e) {
            LOGGER.error(ExceptionUtil.getStackTraceFromException(e));
        }
    }
}
